package com.simpou.commons.utils.reflection;

import com.simpou.commons.utils.validation.Assertions;

import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Auxilia nas operações que utilizam Java Generics.
 *
 * @author Jonas Pereira
 * @since 2012-05-12
 * @version 2012-12-20
 */
public class Generics {

    /**
     * @param t Objeto genérico.
     * @return Classe genérica do objeto.
     */
    @SuppressWarnings("unchecked")
    public static <T> Class<T> getClass(final T t) {
        return (Class<T>) t.getClass();
    }

    /**
     * @param type Tipo dos elementos que o array irá conter.
     * @param size Tamanho do array.
     * @return Array do tamanho e tipo especificado.
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] newArray(final Class<T> type, final int size) {
        return (T[]) Array.newInstance(type, size);
    }

    /**
     * <p>toArray.</p>
     *
     * @param clasz a {@link java.lang.Class} object.
     * @param collection a {@link java.util.Collection} object.
     * @param <T> a T object.
     * @return an array of T objects.
     */
    public static <T> T[] toArray(final Class<T> clasz,
            final Collection<T> collection) {
        //isto é seguro em tempo de compilação
        @SuppressWarnings("unchecked")
        T[] emptyArray = (T[]) Array.newInstance(clasz, 0);

        return collection.toArray(emptyArray);
    }

    /**
     * @param genericType Tipo genérico de uma classe, campo ou outro
     * parametrizável.
     * @return Tipos parametrizados.
     * @throws ClassNotFoundException ClassNotFoundException.
     * @throws IllegalArgumentException Se tipo genérico não está parametrizado
     * ou se há multiplos tipos parametrizados.
     */
    public static Class<?> getParameterizedType(final Type genericType) throws ClassNotFoundException {
        final List<Class<?>> types = getParameterizedTypes(genericType);
        if (types.isEmpty()) {
            throw new IllegalArgumentException("Raw type.");
        } else if (types.size() > 1) {
            throw new IllegalArgumentException("Multiples types is parameterized.");
        } else {
            return types.get(0);
        }
    }

    /**
     * @param genericType Tipo genérico de uma classe, campo ou outro
     * parametrizável.
     * @return Tipos parametrizados.
     * @throws ClassNotFoundException ClassNotFoundException.
     */
    public static List<Class<?>> getParameterizedTypes(final Type genericType)
            throws ClassNotFoundException {
        ParameterizedType pt = Assertions.instanceOf(ParameterizedType.class,
                genericType);
        List<Class<?>> listTypes = new ArrayList<Class<?>>();
        Type[] typeArguments = pt.getActualTypeArguments();

        for (Type type : typeArguments) {
            final Class<?> typeClass;
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                typeClass = (Class<?>) parameterizedType.getRawType();
            } else if (type instanceof TypeVariable) {
                throw new UnsupportedOperationException(TypeVariable.class.getName() + " types is not supported. Probably the parameterized type is defined by other generic class.");
            } else {
                typeClass = (Class<?>) type;
            }
            listTypes.add(typeClass);
        }

        return listTypes;
    }
}
